#include "os.h"

#include <thread>
#include <algorithm>
#include <stdexcept>

#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  #include <process.h>
  #include <windows.h>
  #include <io.h>
  #include <direct.h>
# if defined(__MINGW32__)
    #include <share.h>
# endif
#else
  #include <sys/types.h>
  #include <unistd.h>
#endif

#include "duration.h"

namespace afcore {

int GetPid() noexcept {
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  return static_cast<int>(::GetCurrentProcessId());
#else
  return static_cast<int>(::getpid());
#endif
}

size_t GetThreadId() noexcept {
  return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id()));
}

bool IsColorTerminal() noexcept {
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  return true;
#else
  static constexpr std::array<const char *, 14> terms = {
        {"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}};

    const char *env_p = std::getenv("TERM");
    if (env_p == nullptr)
    {
        return false;
    }

    static const bool result =
        std::any_of(terms.begin(), terms.end(), [&](const char *term) { return std::strstr(env_p, term) != nullptr; });
    return result;
#endif
}

bool InTerminal(FILE *file) noexcept {
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  return ::_isatty(_fileno(file)) != 0;
#else
  return ::isatty(fileno(file)) != 0;
#endif
}

RFileName DirName(RFileName path) {
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  std::replace(path.begin(), path.end(), '/', g_floder_sep);
#endif
  auto pos = path.find_last_of(g_floder_sep);
  return pos != RFileName::npos ? path.substr(0, pos) : RFileName{};
}

bool PathExists(const RFileName &filename) noexcept {
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  auto attribs = ::GetFileAttributesA(filename.c_str());
  return attribs != INVALID_FILE_ATTRIBUTES;
#else
  struct stat buffer;
  return (::stat(filename.c_str(), &buffer) == 0);
#endif
}

int Remove(const RFileName &filename) noexcept {
  return std::remove(filename.c_str());
}

int RemoveIfExists(const RFileName &filename) noexcept {
  return PathExists(filename) ? Remove(filename) : 0;
}


static bool Mkdir(const RFileName& path) {
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  return ::_mkdir(path.c_str()) == 0;
#else
  return ::mkdir(path.c_str(), mode_t(0755)) == 0;
#endif
}

bool CreateDir(RFileName path) {
  if (PathExists(path)) {
    return true;
  }
  if (path.empty()) {
    return false;
  }
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  std::replace(path.begin(), path.end(), '/', g_floder_sep);
#endif
  size_t search_offset = 0;
  do
  {
    auto token_pos = path.find(g_floder_sep, search_offset);
    if (token_pos == RFileName ::npos) {
      token_pos = path.size();
    }

    auto subdir = path.substr(0, token_pos);

    if (!subdir.empty() && !PathExists(subdir) && !Mkdir(subdir)) {
      return false;
    }
    search_offset = token_pos + 1;
  } while (search_offset < path.size());

  return true;
}

bool Fileopen(FILE **fp, const RFileName &filename, const RFileName &mode) {
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  *fp = ::_fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO);
#else
  *fp = ::fopen((filename.c_str()), mode.c_str());
#endif
  return *fp == nullptr;
}

void SleepMillis(int milliseconds) noexcept {
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  ::Sleep(milliseconds);
#else
  std::this_thread::sleep_for(RMicroseconds(milliseconds));
#endif
}

std::string FileNameToString(const RFileName &filename) {
  return filename;
}

size_t FileSize(FILE *f) {
  if (nullptr == f) {
    throw(std::invalid_argument("Failed getting file size. fd is null"));
  }
#if AFCORE_PLATFORM == AFCORE_PLATFORM_WIN
  int fd = ::_fileno(f);
# if _WIN64 // win64
  __int64 ret = ::_filelengthi64(fd);
  if (ret >= 0) {
    return static_cast<size_t>(ret);
  }
# else // win32
  long ret = ::_filelength(fd);
  if (ret >= 0) {
    return static_cast<size_t>(ret);
  }
# endif
#else
  struct stat64 st;
  if (::fstat64(fd, &st) == 0) {
    return static_cast<size_t>(st.st_size);
  }
#endif
  throw(std::invalid_argument("Failed getting file size from fd"));
  return 0;
}

} // !namespace afcore